#include <stdio.h>
#include <string.h>
#include <strings.h>

#include "gttl.h"
#include "redismodule.h"
#include "server.h"

#define DEFAULT_TTL 60 * 1000

long long append_ttl;

RedisModuleCtx *module_ctx;

int shouldSkipCmd(RedisModuleCommandFilterCtx *ctx, int argc) {
  const char *cmd = RedisModule_StringPtrLen(RedisModule_CommandFilterArgGet(ctx, 0), NULL);

  if (!strcasecmp(cmd, "SET")) {
    for (int i = 3; i < argc - 1; i++) {
      const char *arg = RedisModule_StringPtrLen(RedisModule_CommandFilterArgGet(ctx, i), NULL);
      if ((!strcasecmp(arg, "PX") || !strcasecmp(arg, "EX") ||
           !strcasecmp(arg, "PXAT") || !strcasecmp(arg, "EXAT"))) {
        return 1;
      }
    }
  }

  if (!strcasecmp(cmd, "DEL") || !strcasecmp(cmd, "EXPIRE")) {
    return 1;
  }
  return 0;
}

/* 
* 在命令完成前为key追加ttl
*/
void appendTtlFilter(RedisModuleCommandFilterCtx *ctx) {
  int argc = RedisModule_CommandFilterArgsCount(ctx);
  if (argc <= 1) {
    return;
  }

  int is_dirty = RedisModule_CommandFilterDataIsDirty(ctx);
  //RedisModule_Log(NULL, LOG_NOTICE_FLAG, "--> args:%d, dirty:%d", argc, is_dirty);

  if (is_dirty && !shouldSkipCmd(ctx, argc)) {
    RedisModuleKey *key = RedisModule_OpenKey(module_ctx, RedisModule_CommandFilterArgGet(ctx, 1), REDISMODULE_READ | REDISMODULE_WRITE);
    int type = RedisModule_KeyType(key);
    if (type != REDISMODULE_KEYTYPE_EMPTY) {
      RedisModule_SetExpire(key, append_ttl);
    }
    RedisModule_CloseKey(key);

    /* RedisModuleString *k = RedisModule_CommandFilterArgGet(ctx, 1);
    RedisModuleCallReply *reply = RedisModule_Call(module_ctx, "EXPIRE", "cl", "k", 60000);
    if (!reply) {
      RedisModule_ReplyWithError(module_ctx, "NULL reply returned");
    } else {
      RedisModule_ReplyWithCallReply(module_ctx, reply);
      RedisModule_FreeCallReply(reply);
    } */

  }
}

/*
* config function
*/
long long getTtlConfigCommand(const char *name, void *privdata) {
    REDISMODULE_NOT_USED(name);
    return (*(long long *) privdata);
}

int setTtlConfigCommand(const char *name, long long new, void *privdata, RedisModuleString **err) {
    REDISMODULE_NOT_USED(name);
    REDISMODULE_NOT_USED(err);
    *(long long *) privdata = new;
    RedisModule_Log(NULL, LOG_NOTICE_FLAG, "update default-ttl config: %lld", new);
    return REDISMODULE_OK;
}

/*
* load module
*/
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
  REDISMODULE_NOT_USED(argv);
  REDISMODULE_NOT_USED(argc);

  if (RedisModule_Init(ctx, "gttl", 1, REDISMODULE_APIVER_1) ==
      REDISMODULE_ERR) {
    return REDISMODULE_ERR;
  }

  RedisModuleCommandFilter *filter;
  if ((filter = RedisModule_RegisterCommandPostFilter(
           ctx, appendTtlFilter, REDISMODULE_CMDFILTER_NOSELF)) == NULL) {
    RedisModule_Log(ctx, LOG_WARNING_FLAG, "register ttl filter fail");
    RedisModule_Free(filter);
    RedisModule_Free(ctx);
    return REDISMODULE_ERR;
  }
  
  int result = 0;
  result |= RedisModule_RegisterNumericConfig(ctx, "default-ttl", DEFAULT_TTL, REDISMODULE_CONFIG_DEFAULT, 
            -1, (unsigned)(~0) >> 1, getTtlConfigCommand, setTtlConfigCommand, NULL, &append_ttl);
  result |= RedisModule_LoadConfigs(ctx);
  if (result) {
    RedisModule_Log(ctx, LOG_WARNING_FLAG, "register ttl config fail");
    RedisModule_Free(filter);
    RedisModule_Free(ctx);
    return REDISMODULE_ERR;
  }

  module_ctx = RedisModule_GetThreadSafeContext(NULL);
  RedisModule_Log(ctx, LOG_NOTICE_FLAG, "register ttl filter success, ttl:%lld", append_ttl);

  return REDISMODULE_OK;
}

int RedisModule_OnUnload(RedisModuleCtx *ctx) {
  RedisModule_FreeThreadSafeContext(module_ctx);
  return REDISMODULE_OK;
}